package com.cy.juc;

import com.cy.util.U;
import org.testng.annotations.Test;
import sun.plugin2.gluegen.runtime.CPU;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class CompletableFuture_BSite_Jinjie_Learn {


	// 	1、异步任务的交互
	// 	异步任务交互指将异步任务获取结果的速度相比较，按一定的规则(先到先用进行下一步处理

	//  1.1 applyToEither
	// 		applyToeither(把两个异步任务做比较，异步任务先到结果的，就对先到的结果进行下一步的操作。
	// 		演示案例: 使用最先完成的异步任务的结果
	// 		CompletableFuture<R> applyToEither(CompletableFuture<T>other Function<T,R> func)
	// 异步任务交互指两个异步任务，哪个结果先到，就使用哪个结果  先到先用
	// 比如你准备做公交车回家 101 102 都可以 那么哪个公交车先来 就做哪个回家
	@Test
	public void t1_1() {
		CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
			U.sleep(3);
			U.printThreadLog("公交车1 来了!!");
			return "公交车1";
		});
		CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
			U.sleep(1);
			U.printThreadLog("公交车2 来了!!");
			return "公交车2";
		});
		U.sleep(5);
		System.out.println(future1.applyToEither(future2, s -> s).join());
	}

	// 1.2 acceptEither
	// acceptEither ()把两个异步任务做比较，异步任务先到结果的，就对先到的结果进行下一步操作(消费使用)。
	@Test
	public void t1_2() {
		CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
			U.sleep(3);
			U.printThreadLog("公交车1 来了!!");
			return "公交车1";
		});
		CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
			U.sleep(1);
			U.printThreadLog("公交车2 来了!!");
			return "公交车2";
		});
		U.sleep(5);
		// 这里注意这里打印的是慢的那个 因为他只是消费
		System.out.println(future1.acceptEither(future2, U::printThreadLog).join());
	}

	// 1.3 runAfterEither
	// 如果不关心最先到达的结果，只想在有一个异步任务先完成时得到完成的通知，可以使用 runafterEither)，以下是它的相关方法:
	@Test
	public void t1_3() {
		CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
			U.sleep(3);
			U.printThreadLog("公交车1 来了!!");
			return "公交车1";
		});
		CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
			U.sleep(1);
			U.printThreadLog("公交车2 来了!!");
			return "公交车2";
		});
		U.sleep(5);
		// 这里注意这里打印的是慢的那个 因为他只是消费
		System.out.println(future1.runAfterEither(future2, () -> U.printThreadLog("有一个任务完成了")).join());
	}

	// thenApply	thenAccept	thenRun
	// 对上一个异步任务的结果进行操作( 转换-消费-通知)
	// applyToEither runAfterEither acceptEither
	// 对两个异步任务先到的结果进行操作(转换-消费-通知)


	// 2 get  join 区别
	// 使用时，我们发现，get0 抛出检查时异常，需要程序必须处理，而jin0 方法抛出运行时异常，程序可以不处理。所以，join 更适合用
	// 在流式编程中
	@Test
	public void t2_0() {
		CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "res");
		try {
			System.out.println(future.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		System.out.println(future.join());
	}


	// 3、ParallelStream VS CompletableFuture
	// CompletableFuture 虽然提高了任务并行处理的能力，如果它和 Stream AP1 结合使用，能否进一步多个任务的并行处理能力呢?同时，对于 stream API 本身就提供了并行流ParallelStream，它们有什么不同呢?
	// 我们将通过一个耗时的任务来体现它们的不同，更重要地是，我们能进一步加强 completableFuture 和 stream API 的结合使用，同时搞清楚CompletableFuture 在流式操作的优势
	@Test
	public void t3_0() {
		List<MyTask> myTasks = IntStream.range(0, 10).mapToObj(x -> new MyTask(1)).collect(Collectors.toList());
		List<Integer> collect = myTasks.stream().map(MyTask::doWork).collect(Collectors.toList());
		System.out.println(collect);
	}

	// 依赖于你的CPU有几个逻辑处理器 就有几个并行
	@Test
	public void t3_1() {
		List<MyTask> myTasks = IntStream.range(0, 10).mapToObj(x -> new MyTask(1)).collect(Collectors.toList());
		List<Integer> collect = myTasks.parallelStream().map(MyTask::doWork).collect(Collectors.toList());
		System.out.println(collect);
	}

	@Test
	public void t3_2() {
		List<MyTask> myTasks = IntStream.range(0, 10).mapToObj(x -> new MyTask(1)).collect(Collectors.toList());
		ExecutorService executor = Executors.newFixedThreadPool(Math.min(Runtime.getRuntime().availableProcessors(), myTasks.size()));
		CompletableFuture.allOf(myTasks.stream().map(task ->
				CompletableFuture.supplyAsync(task::doWork, executor)
		).toArray(CompletableFuture[]::new)).join();
	}

	// 3.3 合理的配置线程池中的线程数
	// 正如我们看到的，CompletableFuture 可以更好地控制线程池中线程的数量，而 ParallelStream 不能.
	// 问题1:如何选用 CompletableFuture 和 ParallelStream ?
	// 如果你的任务是IO密集型的，你应该使用CompletableFuture;
	// 如果你的任务是CPU密集型的，使用比处理器更多的线程是没有意义的，所以选择Parallelstream ，因为它不需要创建线程池，更容易使
	// 用。
	// 问题2: 0密集型任务和CPU密集型任务的区别?
	// CPU密集型也叫计算密集型，此时，系统运行时大部分的状况是CPU占用率近乎100%，0在很短的时间就可以完成，而CPU还有许多运算要处理，
	// CPU 使用率很高。比如说要计算1+2+3+...+ 10万亿、天文计算、圆周率后几十位等，都是属于CPU密集型程序。
	// CPU密集型任务的特点: 大量计算，CPU占用率一般都很高，I/O时间很短lo密集型指大部分的状况是CPU在等I/0(硬盘/内存)的读写操作，但CPU的使用率不高。简单的说，就是需要大量的输入输出，例如读写文件、传输文件、网络请求。
	// IO密集型任务的特点: 大量网络请求，文件操作，CPU运算少，很多时候CPU在等待资源才能进一步操作。

	// 问题3: 既然要控制线程池的数量，多少合适呢?
	// 如果是CPU密集型任务，就需要尽量压榨CPU，参考值可以设为 Ncpu+1如果是IO密集型任务，参考值可以设置为 2*Ncpu，其中Ncpu 表示核心数注意的是:



	// 1、如果这个异步任务是一个非阻塞代码 (也就是计算密集型代码)，那么直接用即可(底层是ForkJoinPool)。这个在就不再阐述原理了，在线程池那集有讲过。
	// 2、如果这个异步任务是一个IO阻塞型代码，那么就需要使用/额外的线程池去处理，这个线程池的大小取决于这个任务的IO和cpu时间比，还有和中间任务的时间比，大致确定范围即可

}